package com.zdxlz.fcmp.common.security.config;

import com.zdxlz.fcmp.common.core.constant.enums.ResCode;
import com.zdxlz.fcmp.common.core.util.R;
import com.zdxlz.fcmp.common.security.service.UserDetailsServiceImpl;
import com.zdxlz.fcmp.common.security.util.ResponseUtil;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

/**
 * @className SecurityBeanConfig
 * @description
 * @author: aman
 * @date 2025/2/1 13:38
 */
@Configuration
@AllArgsConstructor
public class BeanConfig {

    private UserDetailsServiceImpl userDetailsService;
    private FilterPathsConfig filterPathsConfig;

    /**
     * 白名单请求匹配器
     *
     * @return
     */
    @Bean(name = "excludeMatcher")
    public RequestMatcher excludeMatcher() {
        return new OrRequestMatcher(
                filterPathsConfig.getJwtFilterExcludePaths().stream()
                        .map(AntPathRequestMatcher::new)
                        .toArray(RequestMatcher[]::new)
        );
    }

    /**
     * ict hmac 请求匹配器
     *
     * @return
     */
    @Bean(name = "ictRequestMatcher")
    public RequestMatcher ictRequestMatcher() {
        return new OrRequestMatcher(
                filterPathsConfig.getIctHmacFilterPaths().stream()
                        .map(AntPathRequestMatcher::new)
                        .toArray(RequestMatcher[]::new)
        );
    }

    /**
     * 初始化密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 初始化身份认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return new ProviderManager(authenticationProvider);
    }

    /**
     * cors 配置
     */
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("*"));
        config.setAllowedHeaders(List.of("*"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowCredentials(true);
        config.setExposedHeaders(List.of("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        source.registerCorsConfiguration("/**", config);
        return source;
    }

    /**
     * 自定义访问拒绝异常处理器
     *
     * @return
     */
    @Bean
    public AccessDeniedHandler customAccessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            R<Object> failed = R.failed(ResCode.ERR_USER_ACCESS_DENIED.getCode(), ResCode.ERR_USER_ACCESS_DENIED.getReason());
            ResponseUtil.sendErrorResponse(response, failed);
        };
    }

    /**
     * 自定义认证失败处理器
     *
     * @return
     */
    @Bean
    public AuthenticationEntryPoint customAuthenticationEntryPoint() {
        return (request, response, authException) -> {
            R<Object> failed = R.failed(ResCode.ERR_USER_UNAUTHORIZED.getCode(), ResCode.ERR_USER_UNAUTHORIZED.getReason());
            ResponseUtil.sendErrorResponse(response, failed);
        };
    }
}
